﻿using DatabaseSimulation;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml.Serialization;
using ZedGraph;

namespace DatabaseSimulatorGUI
{
    public partial class DatabaseSimulatorGUI : Form
    {
        #region Input Properties
        public double WritePercentage
        {
            get { return (double)WritePercentageVal.Value; }
            set { WritePercentageVal.Value = (decimal)value; }
        }

        public int CpuTimeMean
        {
            get { return (int)AverageCpuTimeVal.Value; }
            set { AverageCpuTimeVal.Value = value; }
        }

        public int CpuTimeStdDev
        {
            get { return (int)CpuTimeStdDevVal.Value; }
            set { CpuTimeStdDevVal.Value = value; }
        }

        public int IoTimeMean
        {
            get { return (int)AverageIOTimeVal.Value; }
            set { AverageIOTimeVal.Value = value; }
        }

        public int IoTimeStdDev
        {
            get { return (int)IOTimeStdDevVal.Value; }
            set { IOTimeStdDevVal.Value = value; }
        }

        public int NumberOfOperationsMean
        {
            get { return (int)AverageActionsPerTransactionVal.Value; }
            set { AverageActionsPerTransactionVal.Value = value; }
        }

        public int NumberOfOperationsStdDev
        {
            get { return (int)ActionsPerTransactionsStdDevVal.Value; }
            set { ActionsPerTransactionsStdDevVal.Value = value; }
        }

        public int NumberOfCpus
        {
            get { return (int)NumberOfCpusVal.Value; }
            set { NumberOfCpusVal.Value = value; }
        }

        public int NumberOfHardDrives
        {
            get { return (int)NumberOfHardDrivesVal.Value; }
            set { NumberOfHardDrivesVal.Value = value; }
        }

        public int NumberOfObjects
        {
            get { return (int)NumberOfObjectsVal.Value; }
            set { NumberOfObjectsVal.Value = value; }
        }

        public int Milliseconds
        {
            get { return (int)NumberOfMillisecondsVal.Value; }
            set { NumberOfMillisecondsVal.Value = value; }
        }

        public int[] MultiProgrammingLevels
        {
            get { return MultiprogrammingLevelVal.Text.Split(new char[] { ' ', ',', '-' }, StringSplitOptions.RemoveEmptyEntries).Select(i => int.Parse(i)).ToArray(); }
            set { MultiprogrammingLevelVal.Text = String.Join(", ", value); }
        }

        private int TwoPhaseLockingDeadLockTimeout
        {
            get { return (int)TwoPlDeadlockTimeoutVal.Value; }
            set { TwoPlDeadlockTimeoutVal.Value = value; }
        }

        private int RestrictivePrecedenceReadTimeout1
        {
            get { return (int)RpReadTimeout1Val.Value; }
            set { RpReadTimeout1Val.Value = value; }
        }

        private int RestrictivePrecedenceReadTimeout2
        {
            get { return (int)RpReadTimeout2Val.Value; }
            set { RpReadTimeout2Val.Value = value; }
        }

        private int RestrictivePrecedenceWaitToCommitTimeout
        {
            get { return (int)RpWaitTimeoutVal.Value; }
            set { RpWaitTimeoutVal.Value = value; }
        }
        #endregion

        #region Gui Properties
        private bool ResultsTabsEnabled = false;
        private string SettingsPath = ConfigurationManager.AppSettings["SettingsPath"];

        private Dictionary<BlockingProtocols, Color> GraphLineColors = new Dictionary<BlockingProtocols, Color>() { };
        private Dictionary<BlockingProtocols, Color> GraphLine2Colors = new Dictionary<BlockingProtocols, Color>() { };
        private Dictionary<BlockingProtocols, Color> GraphSymbolColors = new Dictionary<BlockingProtocols, Color>() { };
        #endregion

        private Dictionary<BlockingProtocols, List<SimulationResult>> SimulationResults = new Dictionary<BlockingProtocols, List<SimulationResult>>();

        public DatabaseSimulatorGUI()
        {
            InitializeComponent();
            this.GraphLineColors[BlockingProtocols.TwoPL] = Color.DarkRed;
            this.GraphLine2Colors[BlockingProtocols.TwoPL] = Color.Red;
            this.GraphSymbolColors[BlockingProtocols.TwoPL] = Color.DarkRed;
            this.GraphLineColors[BlockingProtocols.RestrictivePrecedence] = Color.DarkBlue;
            this.GraphLine2Colors[BlockingProtocols.RestrictivePrecedence] = Color.Blue;
            this.GraphSymbolColors[BlockingProtocols.RestrictivePrecedence] = Color.DarkBlue;
            LoadState();
        }

        private SimulationResult Run2plSimulation(TransactionGenerator transactionGenerator, int multiprogrammingLevel)
        {
            Database twoPhaseLockingDatabase = new TwoPhaseLockingDatabase(transactionGenerator, this.NumberOfObjects, this.NumberOfHardDrives, this.NumberOfCpus, this.Milliseconds, this.TwoPhaseLockingDeadLockTimeout);

            SimulationResult twoPhaseLockingResult = twoPhaseLockingDatabase.RunSimulation(transactionGenerator, multiprogrammingLevel);

            return twoPhaseLockingResult;
        }

        private SimulationResult RunRpSimulation(TransactionGenerator transactionGenerator, int multiprogrammingLevel)
        {
            Database restrictivePrecedenceDatabase = new RestrictivePresedenceDatabase(transactionGenerator, this.NumberOfObjects, this.NumberOfHardDrives, this.NumberOfCpus, this.Milliseconds, this.RestrictivePrecedenceReadTimeout1, this.RestrictivePrecedenceReadTimeout2, this.RestrictivePrecedenceWaitToCommitTimeout);

            SimulationResult restrictivePrecedenceResult = restrictivePrecedenceDatabase.RunSimulation(transactionGenerator, multiprogrammingLevel);

            return restrictivePrecedenceResult;
        }

        #region Button Clicks
        private void StartButton_Click(object sender, EventArgs e)
        {
            StartButton.Enabled = ResultsTabsEnabled = false;
            ProgressBar.Value = 0;
            DisableAllControls();
            SaveState();
            BackgroundWorker.RunWorkerAsync();
        }

        private void DefaultsButton_Click(object sender, EventArgs e)
        {
            
        }

        private void ViewLogButton_Click(object sender, EventArgs e)
        {
            //System.Diagnostics.Process.Start(LogPath);
        }

        private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            foreach (BlockingProtocols protocol in Enum.GetValues(typeof(BlockingProtocols)))
            {
                RunSimulation(protocol);
            }

            this.Invoke(new MethodInvoker(delegate { SimulationComplete(); }));
        }

        private void RunSimulation(BlockingProtocols protocol)
        {
            Random random = new Random(0);
            GaussianRandom gaussianRandom = new GaussianRandom(random);
            DatabaseOperationGenerator operationGenerator = new DatabaseOperationGenerator(random, gaussianRandom, this.WritePercentage, this.CpuTimeMean, this.CpuTimeStdDev, this.IoTimeMean, this.IoTimeStdDev);
            TransactionGenerator transactionGenerator = new TransactionGenerator(operationGenerator, random, gaussianRandom, this.NumberOfOperationsMean, this.NumberOfOperationsStdDev);

            List<SimulationResult> simulationResults = new List<SimulationResult>();

            switch (protocol)
            {
                case BlockingProtocols.TwoPL:
                    foreach (int mpl in this.MultiProgrammingLevels)
                    {
                        SimulationResult results = Run2plSimulation(transactionGenerator, mpl);
                        VerifyResults(results, mpl);
                        simulationResults.Add(results);
                    }
                    break;
                case BlockingProtocols.RestrictivePrecedence:
                    foreach (int mpl in this.MultiProgrammingLevels)
                    {
                        SimulationResult results = RunRpSimulation(transactionGenerator, mpl);
                        VerifyResults(results, mpl);
                        simulationResults.Add(results);
                    }
                    break;
            }

            SimulationResults[protocol] = simulationResults;
        }

        private void VerifyResults(SimulationResult result, int multiprogrammingLevel)
        {
            int actualTime = result.TempWriteTime + result.BlockedOnCpuTime + result.BlockedOnHardDriveReadTime + result.BlockedOnHardDriveWriteTime + result.BlockedOnTransactionTime + result.CpuUtilization + result.IoUtilization + result.ElapsedWaitOnObjectsToCommit + result.ElapsedWaitOnTransactionsToCommit;
            int expectedTime = this.Milliseconds * multiprogrammingLevel;
            if (actualTime != expectedTime)
            {
                MessageBox.Show("ERROR");
            }
        }

        private void SimulationComplete()
        {
            SetupGraphs();
            StartButton.Enabled = ResultsTabsEnabled = true;
            ProgressBar.Value = 100;
            EnableAllControls();
        }
        #endregion

        #region Graphs
        private void SetupGraphs()
        {
            double seconds = (this.Milliseconds / 1000.0);
            SetupGraph(ThroughputGraph, i => i.CompletedTransactions / seconds);
            SetupGraph(CPUGraph, i => i.CpuUtilization, i => i.UsefulCpuUtilization);
            SetupGraph(IOGraph, i => i.IoUtilization, i => i.UsefulIoUtilization);
            SetupGraph(BlockingGraph, i => i.TransactionBlocks);
            SetupGraph(BlockedTimeGraph, i => i.BlockedOnTransactionTime, i => i.ElapsedWaitOnTransactionsToCommit);
            SetupGraph(DeadlockGraph, i => i.Deadlocks);            
        }

        private void SetupGraph(ZedGraphControl graph, Func<SimulationResult, double> func, Func<SimulationResult, double> func2 = null)
        {
            graph.GraphPane.CurveList.Clear();
            double[] xCoords = this.MultiProgrammingLevels.Select(i => (double)i).ToArray();

            DrawLines(graph, func, xCoords, GraphLineColors);

            if (func2 != null)
            {
                DrawLines(graph, func2, xCoords, GraphLine2Colors);
            }

            graph.AxisChange();
            graph.Refresh();
        }

        private void DrawLines(ZedGraphControl graph, Func<SimulationResult, double> func, double[] xCoords, Dictionary<BlockingProtocols, Color> lineColors)
        {
            foreach (KeyValuePair<BlockingProtocols, List<SimulationResult>> kvp in this.SimulationResults)
            {
                Color lineColor = lineColors[kvp.Key];
                Color symbolColor = GraphSymbolColors[kvp.Key];
                double[] yCoords = this.SimulationResults[kvp.Key].Select(func).Select(i => (double)i).ToArray();
                PointPairList list = new PointPairList(xCoords, yCoords);

                LineItem line = graph.GraphPane.AddCurve(kvp.Key.ToString(), list, lineColor);
                line.Label.IsVisible = false;
                line.Symbol = new Symbol(SymbolType.Diamond, lineColor);
                line.Symbol.Fill = new Fill(symbolColor);
            }
        }

        private void Graph_MouseClick(object sender, MouseEventArgs e)
        {
            //object nearestObject;
            //int index;
            //ThroughputGraph.GraphPane.FindNearestObject(new PointF(e.X, e.Y), this.CreateGraphics(), out nearestObject, out index);
            //if (nearestObject != null && nearestObject.GetType() == typeof(LineItem))
            //{
            //    //LogResults results = ((LineItem)nearestObject).Color.Equals(GraphSymbolColor) ? LogResults[index] : SecondLogResults[index];
            //    //new ResultsPopup(this.TransactionGenerator.NumberOfThreads[index], results).ShowDialog();
            //}
        }
        #endregion

        #region GUI Events
        private void ReadPercentageVal_ValueChanged(object sender, EventArgs e)
        {
            WritePercentageVal.Value = 1 - ReadPercentageVal.Value;
        }

        private void WritePercentageVal_ValueChanged(object sender, EventArgs e)
        {
            ReadPercentageVal.Value = 1 - WritePercentageVal.Value;
        }

        private void TabControl_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!ResultsTabsEnabled)
            {
                TabControl.SelectedIndex = 0;
            }
        }
        #endregion

        #region Helper Methods
        private bool HasSavedState()
        {
            bool result = false;
            if (File.Exists(SettingsPath))
            {
                try
                {
                    using (StreamReader reader = new StreamReader(SettingsPath))
                    {
                        KeyValuePairSer<decimal[], string> kvp;
                        XmlSerializer serializer = new XmlSerializer(typeof(KeyValuePairSer<decimal[], string>));
                        kvp = ((KeyValuePairSer<decimal[], string>)serializer.Deserialize(reader));
                        result = kvp.Key != null && kvp.Value != null;
                    }
                }
                catch { }
            }
            return result;
        }

        private void LoadState()
        {
            if (HasSavedState())
            {
                KeyValuePairSer<decimal[], string> kvp;
                using (StreamReader reader = new StreamReader(SettingsPath))
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(KeyValuePairSer<decimal[], string>));
                    kvp = (KeyValuePairSer<decimal[], string>)serializer.Deserialize(reader);
                }

                Queue<decimal> values = new Queue<decimal>(kvp.Key);
                foreach (Control control in SetupTab.Controls)
                {
                    NumericUpDown upDown = control as NumericUpDown;
                    if (upDown != null)
                    {
                        upDown.Value = values.Dequeue();
                    }
                }
                MultiprogrammingLevelVal.Text = kvp.Value;
            }
        }

        private void SaveState()
        {
            List<decimal> values = new List<decimal>();
            foreach (Control control in SetupTab.Controls)
            {
                NumericUpDown upDown = control as NumericUpDown;
                if (upDown != null)
                {
                    values.Add(upDown.Value);
                }
            }

            KeyValuePairSer<decimal[], string> kvp = new KeyValuePairSer<decimal[], string>(values.ToArray(), MultiprogrammingLevelVal.Text);

            using (StreamWriter writer = new StreamWriter(SettingsPath))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(KeyValuePairSer<decimal[], string>));
                serializer.Serialize(writer, kvp);
            }
        }

        private void DisableAllControls()
        {
            foreach (Control control in this.Controls)
            {
                if (!(control is TabControl))
                {
                    control.Enabled = false;
                }
            }
            this.Opacity = 0.95;
        }

        private void EnableAllControls()
        {
            foreach (Control control in this.Controls)
            {
                control.Enabled = true;
            }
            this.Opacity = 1.0;
        }

        private void UpdateProgress()
        {
            this.Invoke(new MethodInvoker(delegate
            {
                //ProgressBar.Value = Math.Min(100, FinishedTransactionCount * 100 / TransactionCount);
            }));
        }

        private void UpdateTotalProgress()
        {
            this.Invoke(new MethodInvoker(delegate
            {
                //TotalProgressBar.Value = RunsCompleted * 100 / NumberOfRuns;
            }));
        }
        #endregion
    }
}
